package network;

import java.io.*;
import java.net.*;

import presenter.*;
import signals.*;

/**
 * 
 * @author Benjamin GOUIT et David SOLO RABARIMANANA 
 *
 */
public class ViewNI implements Runnable{
	
	private ChatPresenter chatPres;
	private DatagramSocket socketUDP;
	private boolean connected = false;
	
	private Socket socketTCP = null ;
	private OutputStream out = null ;
	private InputStream in = null ;
	
	private ServerSocket serverSocketTCP = null ;
	private Socket clientSocketTCP = null ;
	
	/**
	 * Constructeur
	 */
	public ViewNI () {
	}

	/**
	 * Constructeur
	 * @param chatPres
	 */
	public ViewNI(ChatPresenter chatPres) {
		this.chatPres = chatPres;
		//Pour test !!PropFile(String filename, long filesize, int file_id)
		//initConnectionUDP ();
	}

	/**
	 * Ouverture et écoute du socket UDP
	 */
	@Override
	public void run() {
		initConnectionUDP ();
		waitMessageUDP ();
	}

	/**
	 * Initialisation de la Connexion UDP sur le port 5500
	 */
	private void initConnectionUDP() {
		try {
			//Utilisation du port 5500 pour UDP
			socketUDP = new DatagramSocket (5500);
		} catch (SocketException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Ecoute du socket UDP
	 */
	private void waitMessageUDP() {
		
		while (!connected) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		while (connected) {
			System.out.println("Waiting on UDP");
			try {
				// Reception d'un message UDP
				//---------------------------
			      byte[] data = new byte[10000];
			      DatagramPacket packet = new DatagramPacket(data, data.length);
			      socketUDP.receive(packet);
	
			        ByteArrayInputStream baos = new ByteArrayInputStream(data);
			      ObjectInputStream oos = new ObjectInputStream(baos);
			      Signal s = (Signal)oos.readObject();
			      
			      // filtrage de l'adresse local
			      if (!((packet.getAddress()).getHostName().startsWith(InetAddress.getLocalHost().getHostName())))
			    	  traitementSignal (s, (packet.getAddress()).getHostName());
			      
			    } catch(Exception e) {
			    	if (connected)
			    		e.printStackTrace();
			    }
		}
	}

	/**
	 * Traitement du signal envoyé par la Remote Application
	 * Lancement d'un message du Chat Presenter selon le signal s envoyé par addr
	 * @param s
	 * @param addr
	 */
	private void traitementSignal(Signal s, String addr) {
		
		if (s.getClass() == Hello.class) {
			System.out.println("Hello received from "+((Hello) s).getUsername()+"@"+addr);
			chatPres.helloReceived(((Hello) s).getUsername(), addr);
		}
		else if (s.getClass() == HelloReply.class) {
			System.out.println("HelloReply received from "+((HelloReply) s).getUsername()+"@"+addr);
			chatPres.performAddNewUser (((HelloReply) s).getUsername(), addr);
		}
		else if (s.getClass() == GoodBye.class) {
			System.out.println("GoodBye received from @"+addr);
			chatPres.goodByeReceived(addr);
		}
		else if (s.getClass() == SendText.class) {
			System.out.println("SendText received from @"+addr);
			String[] conversMembers;
			boolean broadcast;
			if (((SendText) s).getReceivers() == null) {
				conversMembers = new String[1];
				conversMembers[0] = addr;
				broadcast = true;
			}
			else {
				InetAddress[] inetAddrsReceivers = ((SendText) s).getReceivers();
				conversMembers = new String[inetAddrsReceivers.length]; //+1 addr pour l'envoyeur, -1 pour moi
				conversMembers[0] = addr;
				boolean trouve = false;
				for (int i = 1; i < inetAddrsReceivers.length; i++) {
					try {
						if (addr.startsWith(InetAddress.getLocalHost().getHostName()))
							trouve = true;
						else {
							if (!trouve)
								conversMembers[i] = inetAddrsReceivers[i-1].getHostName();
							else
								conversMembers[i] = inetAddrsReceivers[i].getHostName();
						}
					} catch (UnknownHostException e) {
						e.printStackTrace();
					}
				}
				broadcast = false;
			}
			chatPres.textReceived(((SendText) s).getMessage(), conversMembers, broadcast);
		}
		else if (s.getClass() == AskLogin.class) {
			System.out.println("AskLogin received from @"+addr);
			chatPres.loginUnknown(addr);
		}
		else if (s.getClass() == PropFile.class) {
			System.out.println("PropFile received for file "+ ((PropFile) s).getFileID() +" from @"+addr);
			chatPres.propFileReceived(addr, ((PropFile) s).getFileName(),((PropFile) s).getFileSize(), ((PropFile) s).getFileID()) ;
		}
		else if (s.getClass() == AcceptFile.class) {
			System.out.println("AcceptFile received for file "+ ((AcceptFile) s).getFileID() +" from @"+addr);
			chatPres.acceptFile(addr, ((AcceptFile)s).getFileID(), ((AcceptFile)s).accepted(), ((AcceptFile)s).now()) ;
		}
	}

	/**
	 * Envoi d'un "Hello" en broadcast
	 * @param username
	 */
	public void sendHello(String username) {
		System.out.println("Hello sent to broadcast");
		connected = true;
		try {
			Thread.sleep(100);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
		sendSignalUDP (new Hello (username), "255.255.255.255");
	}

	/**
	 * Envoi d'un "Hello Reply" a addr
	 * @param localName
	 * @param addr
	 */
	public void sendHelloReply(String localName, String addr) {
		System.out.println("HelloReply sent to "+localName+"@"+addr);
		sendSignalUDP (new HelloReply (localName), addr);
	}

	/**
	 * Envoi un "GoodBye" en broadcast
	 * @param localUsername
	 */
	public void sendGoodbye (String localUsername) {
		System.out.println("GoodBye sent to broadcast");
		sendSignalUDP (new GoodBye (), "255.255.255.255");
	}

	/**
	 * Envoi de msg a addrs
	 * Si le tableau est vide alors broadcast sinon on envoie aux adresses du tableau
	 * @param addrs
	 * @param msg
	 */
	public void sendText(String[] addrs, String msg) {
		for (String addr : addrs)
			System.out.println("Text sent to @" + addr);
		
		InetAddress[] receivers = new InetAddress[addrs.length];;
		if (!addrs[0].equals("255.255.255.255")) { // tableau d'adresse vide => broadcast
			for (int i = 0; i < addrs.length; i++)
				try {
					receivers[i] = InetAddress.getByName(addrs[i]);
				} catch (UnknownHostException e) {
					e.printStackTrace();
				}
		}
		else
			receivers = null;
		for (String addr : addrs)
			sendSignalUDP (new SendText (msg, receivers), addr);
	}
	
	/**
	 * Envoi du signal "AcceptFile"
	 * avec création d'un socket TCP et recpetion du fichier si accept=true et now=true
	 * @param file
	 * @param addr
	 * @param filesize
	 * @param file_id
	 * @param accept
	 * @param now
	 */
	public void sendAcceptFile(File file, String addr, long filesize, int file_id, boolean accept, boolean now) {
		
		AcceptFile s = new AcceptFile (file_id, accept, now) ;
		
		if (accept) {
			
			if (now) {
				//Accept=1 Now=1 => Creation d'un socket TCP afin de recevoir le fichier
				sendSignalUDP(s, addr) ;
				
				// création du socket de reception
				try {
					serverSocketTCP = new ServerSocket(6500) ;
				} catch (IOException e) {
					System.err.println("Could not listen on port : 6500") ;
					System.exit(-1) ;
				}
				
				try {
					clientSocketTCP = serverSocketTCP.accept() ;
				} catch (IOException e) {
					System.err.println("Accept failed : 6500") ;
					System.exit(-1) ;
				}
				
				try {
					in = clientSocketTCP.getInputStream() ;
					out = new FileOutputStream(file) ;
					
					//transfert du fichier vers un fichier local
					for (int i=0 ; i<filesize ; i++) {
						out.write(in.read()) ;
					}
					
				} catch (IOException e) {
					System.err.println("Problem reception file") ;
				}
				
				try {
					in.close();
					out.close() ;
					clientSocketTCP.close() ;
					serverSocketTCP.close() ;
				} catch (IOException e) {
					System.err.println("Problem closing socket") ;
				}
				
				
				
			} else {
				sendSignalUDP(s, addr) ;
			}
			
		} else {
			sendSignalUDP(s, addr) ;
		}
		
	}
	
	/**
	 * Création d'un socket TCP et envoi d'un fichier
	 * @param addr
	 * @param file
	 * @param id
	 */
	public void sendFile(String addr, File file, int id) {
		
		try {
			socketTCP = new Socket (addr, 6500) ;
			out = socketTCP.getOutputStream() ;
			in = new FileInputStream(file) ;
		} catch (UnknownHostException e) {
			System.err.println("Don't know about host : " + addr) ;
			System.exit(1) ;
		} catch (IOException e) {
			System.err.println("Couldn't get I/O for the connection to : " + addr) ;
			System.exit(1) ;
		}
		
		try {
			while (in.available() > 0) {
				out.write(in.read()) ;
			}
		} catch (IOException e) {
			System.err.println("Error send file to : " + addr);
			System.exit(1) ;
		}
		
		try {
			out.close();
		} catch (IOException e) {
			System.err.println("Error close out") ;
			System.exit(1) ;
		}
		try {
			in.close();
		} catch (IOException e) {
			System.err.println("Error close in") ;
			System.exit(1) ;
		}
		try {
			socketTCP.close();
		} catch (IOException e) {
			System.err.println("Error close socket TCP") ;
			System.exit(1) ;
		}
		
	}
	
	/**
	 * Envoi en UDP d'une demande de login (signal askLogin)
	 * @param addr
	 */
	public void askLogin(String addr) {
		System.out.println("AskLogin sent to @" + addr);
		sendSignalUDP (new AskLogin (), addr);
	}

	/**
	 * Réponse a un "AskLogin()"
	 * @param localName
	 * @param addr
	 */
	public void answerLogin(String localName, String addr) {
		System.out.println("HelloReply sent to @" + addr + " to answer to AskLogin");
		sendSignalUDP (new HelloReply (localName), addr);
	}
	
	/**
	 * Envoi d'un PropFile (proposition de fichier) a addr
	 * @param addr
	 * @param fileName
	 * @param taille
	 * @param id
	 */
	public void propFile(String addr, String fileName, long taille, int id) {
		System.out.println("PropFile sent to @" + addr) ;
		PropFile pf = new PropFile(fileName, taille, id) ;
		sendSignalUDP(pf, addr) ;	
	}

	/**
	 * Fermeture du socket UDP et déconnexion
	 */
	public void closeSocket() {
		connected = false;
		socketUDP.close();
	}
	
	/**
	 * Setter du Chat Presenter
	 * @param cp
	 */
	public void setChatPres (ChatPresenter cp) {
		chatPres = cp;
	}
	
	/**
	 * Envoi d'un message UDP depuis le port 6666
	 * @param s
	 * @param addr
	 */
	private void sendSignalUDP (Signal s, String addr) {
		InetAddress address;
		DatagramSocket socket;
		
		try {
			socket = new DatagramSocket (6666);
			
			// Serialize to a byte array
			ByteArrayOutputStream bStream = new ByteArrayOutputStream();
			ObjectOutput oo = new ObjectOutputStream(bStream); 
			oo.writeObject(s);
			oo.close();

			byte[] serializedMessage = bStream.toByteArray();
			
			
			address = InetAddress.getByName(addr);
			//pour test :
			//address = InetAddress.getLocalHost();
			int port = 5500;
			DatagramPacket packet = new DatagramPacket (serializedMessage, serializedMessage.length, address, port);
			socket.send(packet);
			
			socket.close();
			
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
		catch (IOException e) {
			e.printStackTrace();
		}
	}

}
